<html>
    <head>
        <title>对象扩展</title>
    </head>
    <body>
        <script>
            //  对象扩展

                    /*
                        ABP 提供了一个 "对象扩展系统"，允许你在"不修改相关类的情况下向现有对象添加额外的属性。"

                        这允许扩展"由依赖的模块实现的功能"，尤其是当你想要扩展模块定义的"实体"和"Dto"时。

                        （你自己的对象通常不需要对象扩展系统，因为你可以轻松的将常规属性添加到你自己的类中）
                    */


                    /*
                        一、 IHasExtraProerties接口

                            这个接口，是让一个类"可以被扩展"的接口。 接口内部只是定义了一个 字典Dictionary属性：

                                ExtraPropertyDictionary ExtraProperties { get; }

                            ExtraPropertyDictionary类继承自Dictionary<string, object>类。你可以使用此字典添加或获取额外的属性。


                            1、abp中实现了IHasExtraProerties接口的基类：
                                  ①、AggregateRoot 类
                                  ②、ExtensibleEntityDto类、ExtensibleAuditedEntityDto类
                                  ③、由ExtensibleObject实现，这是一个简单的基类，可以继承任何类型的对象。

                                  因此，如果你从这些类继承，你的类也将是可扩展的。如果没有，你可以随时手动实现它。

                            2、基本扩展方法

                                   虽然你可以直接使用类的 ExtraProperties 属性，但是建议在处理额外属性时使用以下扩展方法：

                                   ①、SetProperty()方法，设置属性
                                            user.SetProperty("Title", "My Title");
                                            user.SetProperty("IsSuperUser", true);

                                        SetPprperty()方法返回当前对象，所以你可以链式调用：
                                            user.SetProperty("Title", "My Title")
                                                .SetProperty("IsSuperUser", true);

                                    ②、GetPropery()方法，用于读取额外属性的值：

                                            var title = user.GetProperty<string>("Title");

                                            if (user.GetProperty<bool>("IsSuperUser"))
                                            {
                                                //...
                                            }
                                            
                                            GetProperty是一个泛型方法，将对象类型作为泛型参数。

                                    ③、非原始类型属性

                                           如果你的属性类型不是原始数据类型(int、bool、enum、string....)，则需要使用返回object的GetProperty的非泛型版本。

                                    ④、HasProperty()方法，用于检查是否存在某个属性：

                                    ⑤、RemoveProperty()方法，用于移除某个属性：



                            3、一些最佳实践
                                    
                                    为属性名称使用魔术字符串是危险的，因为你很容易键入错误的属性名称---它不是类型安全的：

                                    相反，你需要：
                                            ①、 为你的属性名称定义一个常量，并将其用作属性名称。

                                            ②、创建扩展方法以轻松设置你的额外属性

                                            eg:
                                                public static class IdentityUserExtensions
                                                {
                                                    private const string TitlePropertyName = "Title";

                                                    public static void SetTitle(this IdentityUser user, string title)
                                                    {
                                                        user.SetProperty(TitlePropertyName, title);
                                                    }

                                                    public static string GetTitle(this IdentityUser user)
                                                    {
                                                        return user.GetProperty<string>(TitlePropertyName);
                                                    }
                                                }

                                            然后，你就可以轻松地设置或获取额外的属性了：

                                                 user.SetTitle("My Title");
                                                 var title = user.GetTitle();   
                    */

                    /*
                        二、对象扩展管理器

                                 虽然你可以为可扩展对象(实现IHasExtraProperties接口的)设置任意属性，
                                 
                                 但是 ObjectExtensionManager用于"显式定义"可扩展类的额外属性。

                                 显式定义额外属性有一些用例：

                                    1、 允许控制如何在对象到对象映射上处理额外的属性。

                                    2、 允许为属性定义元数据。 例如，你可以在使用EF Core 时将额外的属性映射到数据库中的表字段。

                                ObjectExtensionManager 实现单例模式(ObjectExtensionManager.Instance),你应该在应用程序启动前定义对象扩展。

                                应用程序启动模板有一些预定义的静态类来安全地定义内部的对象扩展。


                                1、AddOrUpdate() 方法 （添加或更新对象）

                                        AddOrUpdate是为对象定义额外属性或更新额外属性的主要方法。

                                        eg:  为 IdentityUser 实体定义额外属性：、

                                            ObjectExtensionManager.Instance.AddOrUpdate<IdentityUser>(options=>{
                                                
                                                options.AddOrUpdateProperty<string>("SocialSecurityNumber");

                                                options.AddOrUpdateProperty<bool>("IsSuperUser");
                                            });

                                2、AddOrUpdateProperty() 方法，(添加或更新属性)

                                            虽然AddOrUpdateProperty可以在options上使用，如前所示，但如果要定义一个额外的属性，也可以使用快捷方式扩展方法：
                                                        
                                                    ObjectExtensionManager.Instance
                                                                    .AddOrUpdateProperty<IdentityUser, string>("SocialSecurityNumber");

                                            有时为多个类型定义一个额外的属性会很实用。你可以使用以下代码，而不是一个接一个地定义：

                                                      ObjectExtensionManager.Instance
                                                                            .AddOrUpdateProperty<string>(
                                                                                new[]
                                                                                {
                                                                                    typeof(IdentityUserDto),
                                                                                    typeof(IdentityUserCreateDto),
                                                                                    typeof(IdentityUserUpdateDto)
                                                                                },
                                                                                "SocialSecurityNumber"
                                                                            );

                                3、属性配置
                                            AddOrUpdateProperty还可以获取一个可以对属性定义执行附加配置的操作：

                                                    ObjectExtensionManager.Instance
                                                                          .AddOrUpdateProperty<IdentityUser, string>(
                                                                                "SocialSecurityNumber",
                                                                                options =>
                                                                                {
                                                                                    //Configure options...
                                                                                });

                                            options有一个名为Configuration的字典，它使对象扩展定义甚至可以扩展EF Core使用它将额外的属性映射到数据库中的表字段。请参阅扩展实体文档。

                                            以下部分解释了基本的属性配置选项：

                                                    ①、默认值： 
                                                            自动为新属性设置默认值，这是属性类型的自然默认值，例如string的null，false或int的0。

                                                            有两种方法可以设置默认值：
                                                                1、 DefaultValue选项属性：
                                                                             DefaultValue选项可以设置为任何值：   
                                                                                    ObjectExtensionManager.Instance
                                                                                                        .AddOrUpdateProperty<IdentityUser, int>(
                                                                                                            "MyIntProperty",
                                                                                                            options =>
                                                                                                            {
                                                                                                                options.DefaultValue = 42;
                                                                                                            });
                                                                                                                  
                                                                2、DefaultValueFactory选项：

                                                                             DefaultValueFactory可以设置为返回默认值的函数：
                                                                                    ObjectExtensionManager.Instance
                                                                                                        .AddOrUpdateProperty<IdentityUser, DateTime>(
                                                                                                            "MyDateTimeProperty",
                                                                                                            options =>
                                                                                                            {
                                                                                                                options.DefaultValueFactory = () => DateTime.Now;
                                                                                                            });
                                                            options.DefaultValueFactory的优先级高于options.DefaultValue。

                                                            提示：仅当默认值可能随时间变化时才使用DefaultValueFactory选项（如本例中的DateTime.Now）。如果它是一个常量值，则使用DefaultValue选项。
                    */

                    /*
                        三、验证
                                你可能希望为你定义的额外属性添加一些验证规则。
                                AddOrUpdateProperty方法选项允许两只执行验证的方式：

                                     1、你可以为属性添加数据注释特性。

                                     2、你可以编写一个操作（代码块）来执行自定义验证。


                                当你在 "自动验证的方法"（例如 控制器操作、页面处理程序方法、应用程序服务方法...）中使用对象时，验证就会起作用。
                                
                                因此，每当验证扩展对象时，都会验证所有额外的属性。


                                1、数据注解特性

                                        所有数据注解特性都对额外属性有效.
                                        eg:

                                            ObjectExtensionManager.Instance
                                                                  .AddOrUpdateProperty<IdentityUserCreateDto,string>(
                                                                   "ScialSecurityNumber",
                                                                   options=>
                                                                   {
                                                                        options.Attributes.Add(new RequiredAttribute());

                                                                        options.Attributes.Add(
                                                                            new StringLengthAttribute(32){
                                                                                MinimumLength = 6,
                                                                                MaximumLength = 32
                                                                            }
                                                                        );
                                                                   }
                                                                  );
                                            使用此配置，如果没有提供有效的SocialSecurityNumber值，IdentityUserCreateDto对象将无效。


                                            ★、默认验证特性

                                                   当你创建某些类型的属性时，会自动添加一些特性：

                                                       ①、RequiredAttribute ：   当属性类型为非空原始类型(int\bool\DtateTime\枚举类型)时，会自动添加此特性。

                                                       ②、EnumDataTypeAttribute:  为枚举类型添加此特性，防止无效的枚举值。

                                                   如果你不想要这些特性，可以使用option.Attributes.Clear()方法清除它们。


                                2、自定义验证

                                           如果需要，你可以添加方法（委托）来验证额外属性的自定义操作：
                                                    ObjectExtensionManager.Instance
                                                                        .AddOrUpdateProperty<IdentityUserCreateDto, string>(
                                                                            "SocialSecurityNumber",
                                                                            options =>
                                                                            {
                                                                                //添加自定义验证逻辑
                                                                                options.Validators.Add(context =>
                                                                                {
                                                                                    // context是ValidationContext类的实例，提供了验证的上下文信息，例如属性值。


                                                                                    var socialSecurityNumber = context.Value as string;

                                                                                    if (socialSecurityNumber == null ||
                                                                                        socialSecurityNumber.StartsWith("X"))
                                                                                    {
                                                                                        // 添加验证错误
                                                                                        context.ValidationErrors.Add(
                                                                                            new ValidationResult(
                                                                                                "Invalid social security number: " + socialSecurityNumber,
                                                                                                new[] { "SocialSecurityNumber" }
                                                                                            )
                                                                                        );
                                                                                    }
                                                                                });
                                                                            });

                                            context.ServiceProvider可用于解决高级方案的服务依赖关系；

                                            除了为单个属性添加自定义验证逻辑外，你还可以添加在对象级别执行的自定义验证逻辑。
                                            eg：

                                                ObjectExtensionManager.Instance
                                                                        .AddOrUpdate<IdentityUserCreateDto>(objConfig =>
                                                                        {
                                                                            // 声明带有验证逻辑的额外属性
                                                                            objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
                                                                            {
                                                                                propertyConfig.Attributes.Add(new RequiredAttribute());
                                                                            });

                                                                            // 声明带有验证规则的额外属性
                                                                            objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
                                                                            {
                                                                                propertyConfig.Attributes.Add(new RequiredAttribute());
                                                                            });

                                                                            // 一个基于多个属性的自定义验证逻辑
                                                                            objConfig.Validators.Add(context =>
                                                                            {
                                                                                if (context.ValidatingObject.GetProperty<string>("Password") !=
                                                                                    context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
                                                                                {
                                                                                    // 添加验证错误信息
                                                                                    context.ValidationErrors.Add(
                                                                                        new ValidationResult(  // 添加验证结果
                                                                                            "Please repeat the same password!",
                                                                                            new[] { "Password", "PasswordRepeat" }
                                                                                        )
                                                                                    );
                                                                                }
                                                                            });
                                                                        });
                    */

                    /*
                        四、对象映射

                                假设你向可扩展实体对象添加了一个额外属性，并使用"自动对象映射"，将该实体映射到可扩展DTO类中。
                                在这种情况下，你需要小心了，因为额外属性可能包含客户端不可用的敏感数据。

                                本节提供了一些良好实践来控制对象映射上的额外属性。


                                1、MapExtraPropertiesTo() 方法
                                        MapExtraPropertiesTo() 是ABP提供的一个扩展方法，用于"以受控的方式"将额外属性从一个对象复制到另一个对象。
                                        
                                        eg:

                                            identityUser.MapExtraPropertiesTo(identityUserDto);

                                        MapExtraPropertiesTo需要在两边中定义属性（如上所述）（在本例中为IdentityUser和IdentityUserDto）,以便将值复制到目标对象。
                                        否则，即使对象中确实存在改制，它也不贵复制该值。

                                        有一些方法可以重载此限制：

                                               ①、MappingPropertyDefinitionChecks    
                                               
                                                        MapExtraPropertiesTo获取一个附加参数来控制单个映射操作的定义检查：

                                                            identityUser.MapExtraPropertiesTo(
                                                                        identityUserDto,
                                                                        MappingPropertyDefinitionChecks.None
                                                                    );
                                                    要小心，因为MappingPropertyDefinitionChecks.None没有任何检查就复制所有额外的属性。
                                                    MappingPropertyDefinitionChecks枚举也有其他成员。

                                                    如果要完全禁用属性的定义检查，可以在定义额外属性（或更新现有定义）时执行，如下所示：
                                                           ObjectExtensionManager.Instance
                                                                                .AddOrUpdateProperty<IdentityUser, string>(
                                                                                    "SocialSecurityNumber",
                                                                                    options =>
                                                                                    {
                                                                                        options.CheckPairDefinitionOnMapping = false;
                                                                                    });

                                               ②、忽略某属性的映射
                                                    你可能希望忽略特定映射操作的某些属性：
                                                         identityUser.MapExtraPropertiesTo(
                                                                    identityUserDto,
                                                                    ignoredProperties: new[] {"MySensitiveProp"}
                                                                );
                                                    忽略的属性不会复制到目标对象。

                                               ③、集成AutoMapper
                                                    如果你使用的是AutoMapper库，ABP还提供了一个扩展方法来利用上面定义的MapExtraPropertiesTo方法。
                                                    你可以在映射配置文件中使用MapExtraProperties()方法。
                                                        public class MyProfile : Profile
                                                        {
                                                            public MyProfile()
                                                            {
                                                                CreateMap<IdentityUser, IdentityUserDto>()
                                                                    .MapExtraProperties();
                                                            }
                                                        }

                                                    它具有与MapExtraPropertiesTo方法相同的参数。
                    */

                    /*
                        五、EF Core数据库映射

                            如果你使用EF Core，你可以将额外的属性映射到数据库中的表字段。例子：

                                    ObjectExtensionManager.Instance
                                                        .AddOrUpdateProperty<IdentityUser, string>(
                                                            "SocialSecurityNumber",
                                                            options =>
                                                            {
                                                                options.MapEfCore(b => b.HasMaxLength(32));
                                                            }
                                                        );
                    */
        </script>
    </body>
</html>